package cn.vehicle.biz.impl;

import cn.hutool.core.collection.CollectionUtil;
import cn.vehicle.biz.CameraBiz;
import cn.vehicle.domains.bo.QueryCameraBo;
import cn.vehicle.domains.entity.CameraEntity;
import cn.vehicle.domains.vo.BasePage;
import cn.vehicle.domains.vo.req.camera.*;
import cn.vehicle.domains.vo.res.camera.CameraRes;
import cn.vehicle.enums.WorkModelEnum;
import cn.vehicle.exception.VehicleAccessException;
import cn.vehicle.service.CameraService;
import com.github.pagehelper.Page;
import com.github.pagehelper.PageHelper;
import jakarta.annotation.Resource;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;

import java.util.List;
import java.util.Objects;


/**
 * 相机业务实现类
 * <p>
 * 负责相机的新增、修改、删除、查询、绑定/解绑等业务编排逻辑。
 * 仅做参数与业务校验、领域装配与分页封装，具体的持久化逻辑下沉到 {@link CameraService}。
 * </p>
 *
 * <p>设计约定：</p>
 * <ul>
 *   <li>所有对数据库的写操作统一通过 {@link CameraService} 完成。</li>
 *   <li>涉及到识别相机（DETECTED）与抓拍相机（SNAP）的绑定关系，需保证一对一、类型正确。</li>
 *   <li>删除前需校验是否存在绑定关系，避免产生孤儿数据。</li>
 * </ul>
 */
@Service
public class CameraBizImpl implements CameraBiz {

    @Resource
    private CameraService cameraService;

    /**
     * 新增相机
     *
     * @param req 新增相机请求参数
     *            仅做字段拷贝与入参校验（如需），实际落库交由 {@link CameraService#addCamera(CameraEntity)} 执行。
     */
    @Override
    public void addCamera(AddCameraReq req) {
        CameraEntity cameraEntity = new CameraEntity();
        BeanUtils.copyProperties(req, cameraEntity);

        cameraService.addCamera(cameraEntity);
    }

    /**
     * 更新相机
     *
     * <p>会先校验相机是否存在，再进行字段拷贝与更新。</p>
     *
     * @param req 更新相机请求参数，必须包含主键ID
     * @throws VehicleAccessException 相机不存在时抛出
     */
    @Override
    public void updateCamera(UpdateCameraReq req) {
        checkCameraExist(req.getId());

        CameraEntity updateEntity = new CameraEntity();
        BeanUtils.copyProperties(req, updateEntity);
        cameraService.updateCamera(updateEntity);
    }

    /**
     * 校验相机是否存在
     *
     * @param id 相机主键ID
     * @return 已存在的相机实体
     * @throws VehicleAccessException 不存在时抛出 DATE_NOT_EXIST
     */
    private CameraEntity checkCameraExist(Long id) {
        CameraEntity cameraEntity = cameraService.getCameraById(id);

        if (cameraEntity == null) {
            throw VehicleAccessException.DATE_NOT_EXIST.newInstance("相机不存在");
        }

        return cameraEntity;
    }

    /**
     * 删除相机
     *
     * <p>删除前会校验：
     * <ol>
     *   <li>相机是否存在</li>
     *   <li>是否仍有其它相机通过 associationCameraId 与之绑定（避免误删）</li>
     * </ol>
     * </p>
     *
     * @param id 相机主键ID
     * @throws VehicleAccessException 如存在绑定关系则抛出 DELETE_ERROR
     */
    @Override
    public void deleteCamera(Long id) {
        checkCameraExist(id);

        List<CameraEntity> cameraEntityList = cameraService.queryByAssociationCameraId(id);

        if (CollectionUtil.isNotEmpty(cameraEntityList)) {
            throw VehicleAccessException.DELETE_ERROR.newInstance("有绑定相机未删除");
        }

        cameraService.deleteCamera(id);

    }

    /**
     * 根据ID查询相机详情
     *
     * @param id 相机主键ID
     * @return 相机详情响应对象
     * @throws VehicleAccessException 相机不存在时抛出
     */
    @Override
    public CameraRes getCameraById(Long id) {
        CameraEntity cameraEntity = checkCameraExist(id);

        CameraRes cameraRes = new CameraRes();
        BeanUtils.copyProperties(cameraEntity, cameraRes);
        return cameraRes;
    }

    @Override
    public BasePage<CameraRes> list(CameraListReq req) {
        // 启动分页：仅拦截本线程内的下一条查询（cameraService.selectCameraList）
        Page<CameraEntity> page = PageHelper.startPage(req.getPageNo(), req.getPageSize());

        // 组装查询业务对象（BO）：避免直接把请求对象透传到 Service 层
        QueryCameraBo queryCameraBo = new QueryCameraBo();
        // 拷贝筛选条件（pageNo/pageSize 不需要拷贝到 BO）
        BeanUtils.copyProperties(req, queryCameraBo);

        List<CameraEntity> cameraEntityList = cameraService.selectCameraList(queryCameraBo);
        List<CameraRes> list = cameraEntityList.stream().map(cameraEntity -> {
            CameraRes cameraRes = new CameraRes();
            // DO: 实体到响应对象的属性拷贝（字段名/类型需保持兼容）
            BeanUtils.copyProperties(cameraEntity, cameraRes);
            return cameraRes;
        }).toList();
        // 封装分页结果：总页数、总条数、当前页数据列表
        return BasePage.generateResult(page.getPages(), page.getTotal(), list);
    }

    /**
     * 绑定相机：将“识别相机（DETECTED）”与“抓拍相机（SNAP）”建立一对一关联
     *
     * <p>校验要点：</p>
     * <ul>
     *   <li>ID 不能相同；</li>
     *   <li>两台相机均需存在；</li>
     *   <li>被绑定方必须为抓拍相机 SNAP；</li>
     *   <li>发起绑定方必须为识别相机 DETECTED，且未被绑定；</li>
     * </ul>
     *
     * @param req 绑定请求（id：发起绑定的识别相机；bingId：被绑定的抓拍相机）
     * @throws VehicleAccessException 校验失败时抛出 PARAMS_ERROR
     */
    @Override
    public void bindCamera(BindCameraReq req) {
        // 业务校验：涵盖存在性、类型与唯一性等规则
        bindCameraCheck(req);
        // 仅更新发起绑定的识别相机的 associationCameraId 指向目标抓拍相机
        CameraEntity updateEntity = new CameraEntity();
        updateEntity.setId(req.getId());
        updateEntity.setAssociationCameraId(req.getBingId());
        cameraService.updateCamera(updateEntity);
    }

    /**
     * 解绑相机：解除识别相机与抓拍相机的一对一绑定关系
     *
     * <p>仅允许“识别相机（DETECTED）”执行解绑，且其必须已存在绑定关系。</p>
     *
     * @param req 解绑请求（id：识别相机ID）
     * @throws VehicleAccessException 不满足前置条件时抛出
     */
    @Override
    public void unBindCamera(UnBindCameraReq req) {
        // 解绑前置校验：识别相机类型 + 已绑定
        unBindCameraCheck(req);
        // 仅根据主键执行解绑动作（由 Service 内部将 associationCameraId 置空）
        cameraService.unBindCamera(req.getId());
    }

    /**
     * 解绑前置校验
     *
     * <p>要求：</p>
     * <ul>
     *   <li>相机存在；</li>
     *   <li>相机类型为 DETECTED；</li>
     *   <li>当前已存在绑定关系（associationCameraId 非空）。</li>
     * </ul>
     *
     * @param req 解绑请求
     * @throws VehicleAccessException 不满足条件时抛出 PARAMS_ERROR
     */
    private void unBindCameraCheck(UnBindCameraReq req) {

        CameraEntity cameraEntity = checkCameraExist(req.getId());

        // 判断相机为识别相机
        if (!WorkModelEnum.DETECTED.name().equals(cameraEntity.getWorkMode())) {
            throw VehicleAccessException.PARAMS_ERROR.newInstance("解绑相机必须是识别相机");
        }

        // 判断associationCameraId 不能为空
        if (cameraEntity.getAssociationCameraId() == null) {
            throw VehicleAccessException.PARAMS_ERROR.newInstance("未绑定相机,不能解绑");
        }

    }

    /**
     * 绑定前置校验
     *
     * <p>规则：</p>
     * <ul>
     *   <li>id 与 bingId 不能相同；</li>
     *   <li>两台相机均需存在；</li>
     *   <li>发起绑定的相机类型必须为 DETECTED，且未绑定；</li>
     *   <li>被绑定的相机类型必须为 SNAP。</li>
     * </ul>
     *
     * @param req 绑定请求参数
     * @throws VehicleAccessException 违反任一规则时抛出 PARAMS_ERROR
     */
    private void bindCameraCheck(BindCameraReq req) {
        // 1.id不能一样
        if (Objects.equals(req.getId(), req.getBingId())) {
            throw VehicleAccessException.PARAMS_ERROR.newInstance("绑定相机和被绑相机id不能相同");
        }

        //绑定的相机要存在
        CameraEntity cameraEntity = checkCameraExist(req.getId());

        //被绑的相机要存在
        CameraEntity bindCameraEntity = checkCameraExist(req.getBingId());

        //绑定的相机 不能重复绑定
        if (cameraEntity.getAssociationCameraId() != null) {
            throw VehicleAccessException.PARAMS_ERROR.newInstance("已绑定过相机");
        }

        //绑定的相机必须是识别相机
        if (!WorkModelEnum.DETECTED.name().equals(cameraEntity.getWorkMode())) {
            throw VehicleAccessException.PARAMS_ERROR.newInstance("绑定相机必须是识别相机");
        }

        //被绑的相机必须是抓拍相机
        if (!WorkModelEnum.SNAP.name().equals(bindCameraEntity.getWorkMode())) {
            throw VehicleAccessException.PARAMS_ERROR.newInstance("被绑定相机必须是抓拍相机");
        }
    }
}
